package com.demo.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.demo.entity.Account;
import com.demo.mapper.AccountMapper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * 实现批量生成账号，并插入mysql数据库。
 * 要求：
 * 1、使用多线程并发执行。线程个数为 10个。请使用ThreadPoolExecutor创建线程池，不要使用ExecutorService。每个线程的执行日志需要标志出来。
 * 2、生成账号个数用变量accountTotalNumber表示。
 * 3、账号是由三部分组成，第一部分是前缀8000，第二部分是 序号 seqnumber，序号是由0000000-9999999，的7位数字组成，不足七位的用0补齐，第三部分是字母 "x"。
 * 4、线程池采用分治算法。不同的线程执行不同的任务，根据 seqnumber/线程数 计算出每个线程需要生成账号的数量。分别组装账号
 * 5、生成账号后，批量插入mysql数据库表中。每批次插入数量用batchInsertSize表示。batchInsertSize默认为1000。
 * 6、用完线程池，记得最终关闭线程池
 * 7、操作数据的方法已经集成了mybatis-plus，不需要再写jdbc数据库连接，插入数据库的代码。
 * 8、生成账号的方法单独弄出来。
 * 9、账号表已经有了，Account已经创建，分别是两个字段，id，accountNo
 * 10、请尽量把注释写好。
 * 11、不要使用main方法，已经定义了一个AccountController、AccountService 。直接将主要代码逻辑写到AccountService即可
 */
@Service
@Slf4j
public class AccountService extends ServiceImpl<AccountMapper, Account> {

    private static final Logger logger = LoggerFactory.getLogger(AccountService.class);
    // 线程个数
    private static final int THREAD_COUNT = 20;
    // 账号总数
    private static final int ACCOUNT_TOTAL_NUMBER = 1000000;
    // 每批次插入数量
    private static final int BATCH_INSERT_SIZE = 100;


    /**
     * 使用多线程生成账号并批量插入数据库
     */
    public void generateAndInsertAccounts() throws ExecutionException, InterruptedException {
        logger.info("使用多线程生成账号并批量插入数据库,begin... ");
        // 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                THREAD_COUNT, THREAD_COUNT, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());

        List<Future<List<Account>>> futures = new ArrayList<>();
        try {
            // 每个线程需要生成的账号数量
            int accountsPerThread = ACCOUNT_TOTAL_NUMBER / THREAD_COUNT;
            // 在最后一个线程中需要生成的额外的账号数量
            int extraAccounts = ACCOUNT_TOTAL_NUMBER % THREAD_COUNT;

            //将生成账户列表的任务分成多个线程。'start'和'end'变量用于确定每个线程将生成的帐户范围。'Future'对象用于检索每个线程生成的帐户列表。Lambda函数内的代码由线程池中的线程执行。
            for (int i = 0; i < THREAD_COUNT; i++) {
                // 计算每个线程应处理的账号序号范围
                final int start = i * accountsPerThread;
                final int end = (i == THREAD_COUNT - 1) ? start + accountsPerThread + extraAccounts : start + accountsPerThread;
                Future<List<Account>> future = executor.submit(() -> {
                    logger.info("Thread " + Thread.currentThread().getName() + " started.");
                    List<Account> accounts = new ArrayList<>();
                    for (int j = start; j < end; j++) {
                        // 生成账号
                        accounts.add(new Account(null, generateAccount(j)));
                        // 达到批次插入数量则插入数据库
                        if (accounts.size() == BATCH_INSERT_SIZE) {
                            this.saveBatch(new ArrayList<>(accounts));
                            logger.info("Thread " + Thread.currentThread().getName() + "已生成账号个数:" + j);
                            accounts.clear();
                        }
                    }
                    // 将剩余的账号插入数据库
                    if (!accounts.isEmpty()) {
                        this.saveBatch(accounts);
                        logger.info("Thread " + Thread.currentThread().getName() + " 已生成剩余的账号个数:" + accounts.size());
                    }
                    logger.info("Thread " + Thread.currentThread().getName() + " finished.");
                    return accounts;
                });
                futures.add(future);
            }
            // 等待所有任务执行完毕
            for (Future<List<Account>> future : futures) {
                try {
                    future.get();
                } catch (InterruptedException | ExecutionException e) {
                    logger.error("Error occurred while executing task.", e);
                }
            }
        } finally {
            // 关闭线程池
            if (executor != null) {
                executor.shutdown();
                logger.info("Thread pool shut down.");
            }
        }
    }

    /**
     * 生成账号的方法
     * 账号由前缀8000，7位序号(不足7位前面补0)，后缀x组成
     *
     * @param seqNumber 序号
     * @return 生成的账号
     */
    private String generateAccount(int seqNumber) {
        return "8000" + String.format("%07d", seqNumber) + "x";
    }

}
